package meshGrammar;

import java.awt.event.MouseEvent;
import java.util.ArrayList;

import controlP5.*;

import processing.core.PApplet;
import processing.core.PFont;
import processing.core.PGraphics;

public class MeshGrammarExtrudeApplet extends PApplet {

	/*
	 * comment Faces and Vertices with # 1 in th Objectfile to fix themyou can
	 * create your own rule similar to Rule2 or Rule 1 by extending RuleSplit or
	 * RuleCatmulland constructing it in the Tab subdividepress <SPACE> to
	 * update if not in Live Moduspress <d> to change the display modepress <s>
	 * to save
	 */

	// global variables
	Mesh meshOriginal;
	Mesh meshSubdivided;
	int iteration;
	float extrudeNode, extrudeEdge, extrudeFace;

	// GUI
	PCamera3Dv20 cam;
	boolean doSave;
	boolean doIterate = true;
	ControlP5 cp5;
	PFont font;
	PGraphics guiGraphics;
	RadioButton radioIteration;
	CheckBox checkUpdate;
	Slider[] sliders = new Slider[27];

	// extrusion factors
	float exFace1;
	float exNode1;
	float exEdge1;
	float exFace2;
	float exNode2;
	float exEdge2;

	// analyse values
	final static int CURVATURE = 0;
	final static int AREA = 1;
	final static int PERIMETER = 2;
	final static int JAGGEDNESS = 3;
	final static int PLANARITY = 4;
	final static int HORIZONTALITY = 5;
	int display = -1;
	String[] displayModes = new String[] { "CURVATURE", "AREA", "PERIMETER",
			"JAGGEDNESS", "PLANARITY", "HORIZONTALITY" };
	int nAttributes = displayModes.length;
	float[][] rangeAttributes = new float[nAttributes][2];
	boolean isLive = false;
	boolean isUpdate;

	String path = "/Users/administrator/Desktop/";

	ArrayList<Rule> rules = new ArrayList<Rule>();

	public void setup() {
		
		size(1024, 768, P3D);
		font = this.createFont("Arial", 18);
		this.textFont(font, 18);
		meshOriginal = new Mesh();
		meshOriginal.loadMesh(path + "/" + "cube.obj");
		meshOriginal.construct();
		meshOriginal.normalise();

		cam = new PCamera3Dv20(this, 400, 1);
		setupGUI();
		background(0);

	}

	public void setupGUI() {
		guiGraphics = createGraphics(width, height, JAVA2D);
		cp5 = new ControlP5(this);
		checkUpdate = cp5.addCheckBox("checkBox").setPosition(20, 20)
				.setColorForeground(color(120)).setColorActive(color(255))
				.setColorLabel(color(255)).setSize(10, 10).setItemsPerRow(3)
				.setSpacingColumn(30).setSpacingRow(20).addItem("Live", 0);
		checkUpdate.setArrayValue(new float[] { 1 });

		radioIteration = cp5.addRadioButton("radioButton").setPosition(20, 40)
				.setSize(10, 10).setColorForeground(color(120))
				.setColorActive(color(255)).setColorLabel(color(255))
				.setItemsPerRow(8).setSpacingColumn(15).addItem("0", 0)
				.addItem("1", 1).addItem("2", 2).addItem("3", 3)
				.addItem("4", 4).addItem("5", 5).addItem("6", 6)
				.addItem("7", 7).addItem("8", 8).addItem("9", 9);

		float cY = 60;
		for (int i = 0; i < sliders.length; i++) {
			if (i % 3 == 0)
				cY += 20;
			String name = "";
			int iSlider = (int) (i / 3.);
			if (i % 3 == 0)
				name = "Node" + iSlider;
			if (i % 3 == 1)
				name = "Edge" + iSlider;
			if (i % 3 == 2)
				name = "Face" + iSlider;
			sliders[i] = cp5.addSlider(name).setPosition(20, cY)
					.setRange(-2, 2).setValue(0).setSize(200, 20);
			cY += 20;
		}
		cp5.setAutoDraw(false);
	}

	public void initRules() {
		rules = new ArrayList<Rule>();
		rules.add(new RuleCatmull());
		RuleExtrude rE=new RuleExtrude();
		rE.extrudeFactorNode=100;
		rules.add(rE);
		rules.add(new RuleCatmull());
		RuleExtrude rE2=new RuleExtrude();
		rE2.extrudeFactorNode=100;
		rules.add(rE2);
		rules.add(new RuleCatmull());
		RuleExtrude rE3=new RuleExtrude();
		rE3.extrudeFactorNode=100;
		rules.add(rE3);
		rules.add(new RuleCatmull());
		RuleExtrude rE4=new RuleExtrude();
		rE4.extrudeFactorNode=100;
		rules.add(rE4);
		rules.add(new RuleCatmull());
		
		rules.add(new RuleCatmull());
		rules.add(new RuleCatmull());
		rules.add(new RuleCatmull());
	}

	public void analyse() {
		// analyse
		for (int i = 0; i < meshSubdivided.getNFaces(); i++) {
			MeshFace face = meshSubdivided.getFace(i);
			float[] cAttributes = new float[nAttributes];
			for (int j = 0; j < nAttributes; j++) {
				cAttributes[j] = analyse(face, j);
			}
			if (i == 0) {
				for (int j = 0; j < nAttributes; j++) {
					float cAttr = cAttributes[j];
					rangeAttributes[j][0] = cAttr;
					rangeAttributes[j][1] = cAttr;
				}
			} else {
				for (int j = 0; j < nAttributes; j++) {
					float cAttr = cAttributes[j];
					if (rangeAttributes[j][0] > cAttr) {
						rangeAttributes[j][0] = cAttr;
					}
					if (rangeAttributes[j][1] < cAttr) {
						rangeAttributes[j][1] = cAttr;
					}
				}
			}
		}
	}

	public float analyse(MeshFace face, int attribute) {
		switch (attribute) {
		case CURVATURE:
			return face.getCurvature();
		case AREA:
			return face.getArea();
		case PERIMETER:
			return face.getPerimeter();
		case JAGGEDNESS:
			return face.getJaggedness();
		case PLANARITY:
			return face.getPlanarity();
		case HORIZONTALITY:
			return face.getHorizontality();
		default:
			return 0;
		}
	}

	public void draw() {

		isLive = (checkUpdate.getArrayValue()[0] == 1);
		iteration = (int) radioIteration.getValue();
		if (isLive || isUpdate) {
			if (doIterate) {
				subdivide();
			}
		}

		drawGeom();

		drawGui();
		isUpdate = false;
		// interaction
		if (doSave) {
			meshSubdivided.saveMesh(path + "/out/form_" + timeStamp() + ".obj");
			doSave = false;
		}
	}

	public void drawGui() {
		camera();
		cp5.draw();
		image(guiGraphics, 0, 0);
		if (display >= 0) {
			fill(255);
			text("display: " + displayModes[display], 20, height - 50);
		}
	}

	public void drawGeom() {
		pushMatrix();
		hint(ENABLE_DEPTH_TEST);
		lights();
		// pointLight(200, 200, 210, 5, 5, 5);
		// pointLight(210, 200, 210, -5, -5, -5);
		cam.apply(g);
		if (isLive || isUpdate) {
			background(0);
			noStroke();
			stroke(255);
			// if (display == -1) {

			// }

			for (int i = 0; i < meshSubdivided.getNFaces(); i++) {
				MeshFace face = meshSubdivided.getFace(i);
				float colValue = 255;
				if (display > -1) {
					colValue = map(analyse(face, display),
							rangeAttributes[display][0],
							rangeAttributes[display][1], 0, 255);
					fill(255, 255 - colValue, 255 - colValue);
				} else {
					fill(255);
				}

				face.display3D(g);
				/*Vec3D c=face.getGravityCenter();
				Vec3D n=face.getNormal();
				n.normalize();
				n.mult(0.1f);
				line(c.x,c.y,c.z,c.x+n.x,c.y+n.y,c.z+n.z);*/
			}
			noFill();
			noLights();
		}
		hint(DISABLE_DEPTH_TEST);
		popMatrix();
	}

	public void keyPressed() {
		if (key == 's') {
			doSave = true;
		}
		if (key == ' ') {
			isUpdate = true;
		}
		if (key == 'd') {
			display++;
			if (display == displayModes.length)
				display = -1;
		}
	}

	public void mousePressed(MouseEvent m) {
		if (m.getX()>200){
		cam.mousePressed(m);
		}
		super.mousePressed(m);
	}

	public void mouseDragged(MouseEvent m) {
		if (m.getX()>200){
		cam.mouseDragged(m);
		}
		super.mouseDragged(m);
	}

	public void subdivide() {
		initRules();
		meshSubdivided = meshOriginal.getCopy();
		for (int i = 0; i < iteration; i++) {
			extrudeNode = sliders[i * 3].getValue();
			extrudeEdge = sliders[i * 3 + 1].getValue();
			extrudeFace = sliders[i * 3 + 2].getValue();
			analyse();
			Rule cRule = rules.get(i);
			cRule.setExtrudeFactorEdge(extrudeEdge);
			cRule.setExtrudeFactorNode(extrudeNode);
			cRule.setExtrudeFactorFace(extrudeFace);
			meshSubdivided = cRule.replace(meshSubdivided);
		}
		meshSubdivided.normalise();
		if (display >= 0) {
			analyse();
		}
	}

	public String timeStamp() {
		return this.month() + "_" + this.day() + "_" + this.hour() + "_"
				+ this.minute() + "_" + this.second();
	}

	// splitting rule for smoothing
	class RuleCatmull extends RuleSplit {
		public MeshNode getNodePt(MeshNode n) {
			if (n.getComment() == 1) {
				MeshNode node = new MeshNode(n);
				node.setComment(n.getComment());
				return node;
			}
			ArrayList<MeshFace> nbFaces = n.getFaces();
			Vec3D sumFaces = new Vec3D();
			for (int i = 0; i < nbFaces.size(); i++) {
				MeshFace face = nbFaces.get(i);
				sumFaces.add(face.getGravityCenter());
			}
			sumFaces.div(nbFaces.size());
			Vec3D sumEdges = new Vec3D();
			ArrayList<MeshEdge> nbEdges = n.getEdges();
			for (int i = 0; i < nbEdges.size(); i++) {
				MeshEdge edge = nbEdges.get(i);
				sumEdges.add(edge.getCenter());
			}
			sumEdges.mult(2f / nbEdges.size());
			Vec3D sumPos = new Vec3D(n.x, n.y, n.z);
			sumPos.mult(nbEdges.size() - 3);
			Vec3D newPos = new Vec3D();
			newPos.add(sumFaces);
			newPos.add(sumEdges);
			newPos.add(sumPos);
			newPos.div(nbEdges.size());
			Vec3D normal = n.getNormal();
			normal.mult(getNodeExtrude(n) * extrudeFactorNode);
			newPos.add(normal);
			MeshNode node = new MeshNode(newPos);
			node.setComment(n.getComment());
			return node;
		}

		public MeshNode getFacePt(MeshFace face) {
			Vec3D center = face.getGravityCenter();
			MeshNode node = new MeshNode(center);
			node.setComment(face.getComment());
			if (node.getComment() != 1) {
				Vec3D normal = face.getNormal();
				normal.mult(getFaceExtrude(face) * extrudeFactorFace);
				node.add(normal);
			}
			return node;
		}

		public MeshNode getEdgePt(MeshEdge edge) {
			MeshNode n1 = edge.getNode1();
			MeshNode n2 = edge.getNode2();
			if (n1.getComment() == 1 && n2.getComment() == 1) {
				MeshNode newNode = new MeshNode(edge.getCenter());
				newNode.setComment(1);
				return newNode;
			}
			Vec3D vSum = new Vec3D();
			vSum.add(n1);
			vSum.add(n2);
			int nFace = 0;
			MeshFace face = edge.getFace1();
			if (face != null) {
				nFace++;
				vSum.add(face.getGravityCenter());
			}
			face = edge.getFace2();
			if (face != null) {
				nFace++;
				vSum.add(face.getGravityCenter());
			}
			vSum.div(nFace + 2f);
			Vec3D normal = edge.getNormal();
			normal.mult(getEdgeExtrude(edge) * extrudeFactorEdge);
			vSum.add(normal);
			MeshNode node = new MeshNode(vSum);
			return node;
		}
	}

	// basic splitting rule
	class RuleSplit extends Rule {
		MeshNode[] nodeNodes;
		MeshNode[] edgeNodes;
		MeshNode[] faceNodes;
		Mesh mesh;
		Mesh newMesh;

		RuleSplit() {

		}

		RuleSplit(float extrudeNode, float extrudeEdge, float extrudeFace) {
			this.extrudeFactorNode = extrudeNode;
			this.extrudeFactorEdge = extrudeNode;
			this.extrudeFactorFace = extrudeFace;
		}

		public Mesh replace(Mesh mesh) {
			// TODO Auto-generated method stub
			this.mesh = mesh;
			newMesh = new Mesh();
			collectNodes();
			compose();
			newMesh.construct();
			return newMesh;
		}

		// collecting the new Nodes
		public void collectNodes() {
			nodeNodes = new MeshNode[mesh.getNNodes()];
			edgeNodes = new MeshNode[mesh.getNEdges()];
			faceNodes = new MeshNode[mesh.getNFaces()];
			for (int i = 0; i < mesh.getNFaces(); i++) {
				MeshFace face = mesh.getFace(i);
				MeshNode node = getFacePt(face);
				faceNodes[i] = node;
				newMesh.addNode(node);
			}

			for (int i = 0; i < mesh.getNNodes(); i++) {
				MeshNode n = mesh.getNode(i);
				MeshNode node = getNodePt(n);
				nodeNodes[i] = node;
				newMesh.addNode(node);
			}

			ArrayList<MeshEdge> edges = mesh.getEdges();
			for (int i = 0; i < edges.size(); i++) {
				MeshEdge edge = edges.get(i);
				MeshNode node = getEdgePt(edge);
				edgeNodes[i] = node;
				newMesh.addNode(node);
			}
		}

		public float getFaceExtrude(MeshFace face) {
			return 1;
		}

		public float getEdgeExtrude(MeshEdge edge) {
			return 1;
		}

		public float getNodeExtrude(MeshNode node) {
			return 1;
		}

		public void compose() {
			for (int i = 0; i < mesh.getNFaces(); i++) {
				MeshFace face = mesh.getFace(i);
				for (int ii = 0; ii < face.getNNodes(); ii++) {
					MeshNode n1 = face.getNode(ii);
					MeshNode n2 = face.getNode(ii + 1);
					MeshNode n3 = face.getNode(ii + 2);
					MeshEdge e1 = n1.getEdge(n2);
					MeshEdge e2 = n2.getEdge(n3);
					MeshNode[] newNodes = new MeshNode[4];
					newNodes[0] = (edgeNodes[e1.getId()]);
					newNodes[1] = (nodeNodes[n2.getId()]);
					newNodes[2] = (edgeNodes[e2.getId()]);
					newNodes[3] = (faceNodes[face.getId()]);
					MeshFace newFace = new MeshFace(newNodes);
					newFace.setComment(face.getComment());
					newMesh.addFace(newFace);
				}
			}
		}

		public MeshNode getNodePt(MeshNode n) {
			MeshNode node = new MeshNode(n);
			node.setComment(n.getComment());
			if (n.getComment() == 1) {
				return node;
			}
			Vec3D normal = n.getNormal();
			normal.mult(getNodeExtrude(n) * extrudeFactorNode);
			node.add(normal);
			return node;
		}

		public MeshNode getFacePt(MeshFace face) {
			Vec3D center = face.getGravityCenter();
			MeshNode node = new MeshNode(center);
			node.setComment(face.getComment());
			if (node.getComment() != 1) {
				Vec3D normal = face.getNormal();
				normal.mult(getFaceExtrude(face) * extrudeFactorFace);
				node.add(normal);
			}
			return node;
		}

		public MeshNode getEdgePt(MeshEdge edge) {
			MeshNode n1 = edge.getNode1();
			MeshNode n2 = edge.getNode2();
			MeshNode newNode = new MeshNode(edge.getCenter());
			if (n1.getComment() == 1 && n2.getComment() == 1) {
				newNode.setComment(1);
				return newNode;
			}
			Vec3D normal = edge.getNormal();
			normal.mult(getEdgeExtrude(edge) * extrudeFactorEdge);
			newNode.add(normal);
			return newNode;
		}

	}
}
